package com.basker.pisces.domain.property.meta;

import java.util.Map;

import org.springframework.core.NamedThreadLocal;

import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.IReferenceField;
import com.basker.pisces.core.meta.annotation.DataField;
import com.basker.pisces.core.repository.IDataRepository;
import com.basker.pisces.domain.entity.data.IDataEntity;
import com.basker.pisces.domain.entity.meta.Entity;
import com.basker.pisces.domain.property.InvalidPropertyValueException;
import com.basker.pisces.domain.value.IValueObject;
import com.basker.pisces.domain.value.ReferenceObjectValue;

public class ReferenceProperty extends Property<ReferenceObjectValue> {

    private static final ThreadLocal<Boolean> LOAD_REFERENCE_OBJECT_DISABLE = new NamedThreadLocal<>(
            "LOAD_REFERENCE_OBJECT_DISABLE");
    protected transient IReferenceField referenceField;
    protected transient Entity referenceEntity;
    @DataField
    private String referenceEntityName;

    /**
     * 当前线程恢复用id加载对象的功能
     */
    public static void resumeLoadReferenceObject() {
        LOAD_REFERENCE_OBJECT_DISABLE.remove();
    }

    /**
     * 当前线程对录入的id值直接赋值，不用id加载对象
     */
    public static void suspendLoadReferenceObject() {
        LOAD_REFERENCE_OBJECT_DISABLE.set(Boolean.TRUE);
    }

    @Override
    public void bindField(IDataObjectMeta objectMeta) {
        super.bindField(objectMeta);

        this.referenceField = (IReferenceField) this.field;
    }

    @Override
    public ReferenceObjectValue createValue(Object rawValue) {
        if (rawValue == null) {
            return ReferenceObjectValue.NULL_REFERENCE;
        }

        Object referenceObjectId = this.referenceField.getDataObjectMeta().getPkField().getValue(rawValue);
        return new ReferenceObjectValue(referenceObjectId, rawValue);
    }

    @Override
    public void fillMapObject(final IDataEntity dataEntity, final Object dataObject, final Map<String, Object> mapObject) {
        ReferenceObjectValue value = this.getValue(dataObject);
        if (IValueObject.isEmpty(value)) {
            return;
        }

        mapObject.put(this.getName() + IDataField.ID_FIELD_NAME, value.getDataValue());
        mapObject.put(this.getName(), dataEntity.outputAsMap(this.referenceEntity, value.getReferenceObject()));
    }

    public Entity getReferenceEntity() {
        return referenceEntity;
    }

    public void setReferenceEntity(Entity referenceEntity) {
        this.referenceEntity = referenceEntity;
    }

    public String getReferenceEntityName() {
        return referenceEntityName;
    }

    public void setReferenceEntityName(String referenceEntityName) {
        this.referenceEntityName = referenceEntityName;
    }

    public IReferenceField getReferenceField() {
        return referenceField;
    }

    public final IDataObjectMeta getReferenceObjectMeta() {
        return this.referenceField.getDataObjectMeta();
    }

    @Override
    public void setValue(Object dataObject, Object inputValue) throws InvalidPropertyValueException {
        if (inputValue == null) {
            this.setRefObject(dataObject, null);

            return;
        }

        Object refObject = null;

        if (isIdValue(inputValue)) {

            if (Boolean.TRUE.equals(LOAD_REFERENCE_OBJECT_DISABLE.get())) {
                this.referenceField.getReferenceIdField().setValue(dataObject, inputValue);
                return;
            } else {
                IDataRepository repository = IDataRepository.getInstance();
                refObject = repository.load(this.getReferenceObjectMeta(), inputValue);
            }
        } else {
            // 这里需要做类型校验
            refObject = inputValue;
        }

        this.setRefObject(dataObject, refObject);
    }

    private boolean isIdValue(Object inputValue) {
        return this.referenceField.getReferenceIdField().getFieldClass().isAssignableFrom(inputValue.getClass());
    }

    private void setRefObject(Object dataObject, Object refObject) {
        Object refId = refObject != null ? this.referenceField.getDataObjectMeta().getPkField().getValue(refObject)
                : null;

        this.setRawValue(dataObject, refObject);
        this.referenceField.getReferenceIdField().setValue(dataObject, refId);
    }

}
